package io.gitee.chearnee.fw.audit.aop;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.gitee.chearnee.fw.audit.IAuditUserContext;
import io.gitee.chearnee.fw.audit.annotation.AuditLogAction;
import io.gitee.chearnee.fw.audit.constant.enums.EventResult;
import io.gitee.chearnee.fw.audit.entity.SysAuditLog;
import io.gitee.chearnee.fw.audit.service.SysAuditLogService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author cn
 * @version 1.0
 * @description
 * @date 2021/6/18 14:10
 */

@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class AuditLogAop {

  private final SysAuditLogService sysAuditLogService;
  private final ObjectMapper objectMapper;
  private final ApplicationContext applicationContext;
  private final IAuditUserContext auditUserContext;

  /**
   * 切入点
   */
  @Pointcut("@annotation(io.gitee.chearnee.fw.audit.annotation.AuditLogAction)")
  public void audit() {
    // pointcut
  }

  @Around("audit()")
  public Object aroundExec(ProceedingJoinPoint joinPoint) throws Throwable {
    Object rtn = null;
    EventResult result = EventResult.FAILED;
    long time = System.currentTimeMillis();
    try {
      rtn = joinPoint.proceed();
      time = System.currentTimeMillis() - time;
      result = EventResult.SUCCESS;
      return rtn;
    } catch (Exception e) {
      time = System.currentTimeMillis() - time;
      throw e;
    } finally {
      addAuditLog(joinPoint, rtn, result, time);
    }
  }

  private void addAuditLog(JoinPoint joinPoint, Object res,
      EventResult result, long time) {
    MethodSignature ms = (MethodSignature) joinPoint.getSignature();
    Method method = ms.getMethod();
    // 获取注解的参数信息
    AuditLogAction rootAuditLogAction = method.getDeclaringClass()
        .getAnnotation(AuditLogAction.class);

    AuditLogAction auditLogAction = method.getAnnotation(AuditLogAction.class);

    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
        .currentRequestAttributes()).getRequest();

    SysAuditLog sysAuditLog = new SysAuditLog();

    StringBuilder moduleStringBuilder = new StringBuilder();

    if (rootAuditLogAction != null) {
      if (StringUtils.hasText(rootAuditLogAction.mainModuleName())) {
        moduleStringBuilder.append(rootAuditLogAction.mainModuleName());
      }
      if (StringUtils.hasText(rootAuditLogAction.subModuleName())) {
        if (moduleStringBuilder.length() > 0) {
          moduleStringBuilder.append("/");
        }
        moduleStringBuilder.append(rootAuditLogAction.subModuleName());
      }
    }
    if (auditLogAction != null) {
      if (StringUtils.hasText(auditLogAction.mainModuleName())) {
        if (moduleStringBuilder.length() > 0) {
          moduleStringBuilder.append("/");
        }
        moduleStringBuilder.append(auditLogAction.mainModuleName());
      }
      if (StringUtils.hasText(auditLogAction.subModuleName())) {
        if (moduleStringBuilder.length() > 0) {
          moduleStringBuilder.append("/");
        }
        moduleStringBuilder.append(auditLogAction.subModuleName());
      }

      sysAuditLog.setModuleName(moduleStringBuilder.toString());
      sysAuditLog.setOperationType(auditLogAction.operationType());

      sysAuditLog.setEventType(auditLogAction.eventType());
      sysAuditLog.setEventLevel(auditLogAction.eventLevel());
      sysAuditLog.setExecutionTime(time);

      sysAuditLog.setOperator(auditUserContext.getUserName());
      sysAuditLog.setOperatorId(auditUserContext.getUserId());

      sysAuditLog.setRequestContent(buildRequestContent(request, joinPoint));

      sysAuditLog.setEventResult(result);

      sysAuditLog.setIp(getIpAddr(request));
      sysAuditLog.setApplicationName(
          applicationContext.getEnvironment().getProperty("spring.application.name"));
      sysAuditLog.setEventMemo(buildMemo(sysAuditLog, auditLogAction));
      log.info(sysAuditLog.toString());
      sysAuditLogService.save(sysAuditLog);
    }
  }

  @SneakyThrows
  private String buildRequestContent(HttpServletRequest request, JoinPoint joinPoint) {
    HashMap<String, Object> stringObjectHashMap = new HashMap<>();
    Map<String, String> requestHeader = getRequestHeader(request);
    stringObjectHashMap.put("headers", requestHeader);
    stringObjectHashMap.put("uri", request.getRequestURI());
    stringObjectHashMap.put("params", objectMapper.writeValueAsString(joinPoint.getArgs()));
    return objectMapper.writeValueAsString(stringObjectHashMap);
  }

  private Map<String, String> getRequestHeader(HttpServletRequest request) {
    Map<String, String> head = new HashMap<>();
    Enumeration<String> headerNames = request.getHeaderNames();
    while (headerNames.hasMoreElements()) {
      String headerName = headerNames.nextElement();
      head.put(headerName, request.getHeader(headerName));
    }
    return head;
  }

  private String buildMemo(SysAuditLog sysAuditLog, AuditLogAction auditLogAction) {
    return MessageFormat
        .format("用户【{0}】在【{1}】中,IP为【{2}】", sysAuditLog.getOperator(),
            sysAuditLog.getApplicationName(), sysAuditLog.getIp())
        + MessageFormat.format("于【{0}】对【{1}】进行【{2}】操作,", LocalDateTime.now().format(
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), sysAuditLog.getModuleName(),
        StringUtils.hasText(auditLogAction.operationString()) ? auditLogAction.operationString()
            : auditLogAction.operationType().getDesc())
        + MessageFormat.format("结果为【{0}】", sysAuditLog.getEventResult().getDesc());
  }


  /**
   * 获取用户真实IP地址，不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
   * 可是，如果通过了多级反向代理的话，X-Forwarded-For的值并不止一个，而是一串IP值
   *
   * @return ip
   */
  private String getIpAddr(HttpServletRequest request) {
    String ip = request.getHeader("x-forwarded-for");
    String ipString = "unknown";
    if (ip != null && ip.length() != 0 && !ipString.equalsIgnoreCase(ip)) {
      // 多次反向代理后会有多个ip值，第一个ip才是真实ip
      if (ip.contains(",")) {
        ip = ip.split(",")[0];
      }
    }
    if (ip == null || ip.length() == 0 || ipString.equalsIgnoreCase(ip)) {
      ip = request.getHeader("Proxy-Client-IP");
    }
    if (ip == null || ip.length() == 0 || ipString.equalsIgnoreCase(ip)) {
      ip = request.getHeader("WL-Proxy-Client-IP");
    }
    if (ip == null || ip.length() == 0 || ipString.equalsIgnoreCase(ip)) {
      ip = request.getHeader("HTTP_CLIENT_IP");
    }
    if (ip == null || ip.length() == 0 || ipString.equalsIgnoreCase(ip)) {
      ip = request.getHeader("HTTP_X_FORWARDED_FOR");
    }
    if (ip == null || ip.length() == 0 || ipString.equalsIgnoreCase(ip)) {
      ip = request.getHeader("X-Real-IP");
    }
    if (ip == null || ip.length() == 0 || ipString.equalsIgnoreCase(ip)) {
      ip = request.getRemoteAddr();
    }
    return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
  }
}
